package com.laolang.thresh.module.auth.filter;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.laolang.thresh.framework.common.core.domain.R;
import com.laolang.thresh.framework.common.util.web.ServletUtil;
import com.laolang.thresh.framework.redis.util.RedisUtil;
import com.laolang.thresh.module.auth.config.AnonymousAccessBean;
import com.laolang.thresh.module.auth.consts.AuthConsts;
import com.laolang.thresh.module.auth.consts.AuthRedisConsts;
import com.laolang.thresh.module.auth.domain.LoginUser;
import com.laolang.thresh.module.auth.exception.AuthBusinessException;
import com.laolang.thresh.module.auth.propterties.AuthProperties;
import com.laolang.thresh.module.auth.service.TokenService;
import java.io.IOException;
import java.util.Objects;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * jwt 过滤器.
 *
 * @author laolang
 * @version 0.1
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class JwtFilter extends OncePerRequestFilter {

    private final TokenService tokenService;
    private final RedisUtil redisUtil;
    private final AuthProperties authProperties;
    private final AnonymousAccessBean anonymousAccessBean;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
        FilterChain filterChain) throws ServletException, IOException {
        // 匿名访问处理
        if (anonymouseHandler(request)) {
            filterChain.doFilter(request, response);
            return;
        }

        // 获取 token
        String token = request.getHeader(authProperties.getHeader());
        log.info("token:{}", token);
        // token 为空或格式不正确
        if (StrUtil.isBlank(token) || token.length() <= 7) {
            log.error("token 格式不正确:【{}】", token);
            throw AuthBusinessException.authTokenVerifyFailed();
        }
        token = token.substring(7);

        // 获取 请求头 uuid
        String uuid = request.getHeader(authProperties.getUuidKey());
        if (StrUtil.isBlank(uuid)) {
            log.error("请求头中的 uuid 不存在");
            ServletUtil.renderJson(response, JSONUtil.toJsonStr(R.doOverdue()));
            throw AuthBusinessException.authTokenVerifyFailed();
        }

        // 获取 redis 缓存中的 token 信息
        Object loginUserObj = redisUtil.get(AuthRedisConsts.JWT_PREFIX + uuid);
        if (Objects.isNull(loginUserObj)) {
            log.error("redis 中无登录信息:【{}】", AuthRedisConsts.JWT_PREFIX + uuid);
            ServletUtil.renderJson(response, JSONUtil.toJsonStr(R.doOverdue()));
            throw AuthBusinessException.authTokenVerifyFailed();
        }
        LoginUser loginUser;
        if (loginUserObj instanceof LoginUser) {
            loginUser = (LoginUser) loginUserObj;
        } else {
            log.error("redis 中的登录信息不正确:【{}】", JSONUtil.toJsonStr(loginUserObj));
            ServletUtil.renderJson(response, JSONUtil.toJsonStr(R.doOverdue()));
            throw AuthBusinessException.authTokenVerifyFailed();
        }

        // 判断请求头与 redis 中的 uuid 是否相同
        if (!StrUtil.equals(loginUser.getTokenUuid(), request.getHeader(authProperties.getUuidKey()))) {
            log.error("请求头与 redis 中的 uuid 不相同. 请求头 uuid:【{}】,redis uuid:【{}】",
                request.getHeader(authProperties.getUuidKey()), loginUser.getTokenUuid());
            ServletUtil.renderJson(response, JSONUtil.toJsonStr(R.doOverdue()));
            throw AuthBusinessException.authTokenVerifyFailed();
        }

        // 验证 token
        tokenService.verifyToken(token, loginUser.getJwtSecret());
        // 设置内部请求参数, 保存登录信息
        request.setAttribute(AuthConsts.LOGIN_USER_REQUEST_HEADER_KEY, loginUser);
        // 刷新 token
        redisUtil.expire(AuthRedisConsts.JWT_PREFIX + loginUser.getTokenUuid(), authProperties.getAccessExpire() * 60);

        // security 上下文
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,
            null,
            loginUser.getAuthorities());
        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        log.info("loginUser:{}", JSONUtil.toJsonStr(loginUser));

        filterChain.doFilter(request, response);
    }

    /**
     * 匿名访问处理.
     */
    private boolean anonymouseHandler(HttpServletRequest request) {
        return anonymousAccessBean.getUris().stream().anyMatch(s -> StrUtil.equals(request.getRequestURI(), s));
    }
}
